# install prefix
PREFIX ?= /opt/nxos-sdk

SRC_ROOT_DIR = $(abspath $(CURDIR))
ifeq ($(O),)
BUILD_ROOT_DIR = $(SRC_ROOT_DIR)/build
else
BUILD_ROOT_DIR = $(abspath $(O))
endif

MAKE ?= make
CMAKE ?= cmake
RM ?= rm
CP ?= cp -r
TOUCH ?= touch
ECHO ?= echo
MKDIR ?= mkdir -p
LINK ?= ln -sf

LLVM_PROJ_SRC_DIR = $(SRC_ROOT_DIR)/deps/llvm-project
LIB_XLIBC_SRC_DIR = $(SRC_ROOT_DIR)/deps/libxlibc
LIB_NXOS_SRC_DIR = $(SRC_ROOT_DIR)/deps/libnxos
CMAKE_TOOLCHAIN_DIR = $(SRC_ROOT_DIR)/share/cmake

CLANG_BUILD_DIR = $(BUILD_ROOT_DIR)/clang
LLVM_RUNTIMES_BUILD_DIR_PREFIX = $(BUILD_ROOT_DIR)/runtimes
LIB_NXOS_BUILD_DIR_PREFIX = $(BUILD_ROOT_DIR)/libnxos
LIB_XLIBC_BUILD_DIR_PREFIX = $(BUILD_ROOT_DIR)/libxlibc
INSTALL_SYSROOTS_PREFIX = $(PREFIX)/sysroots


.DEFAULT_GOAL: help
.PHONY: all help distclean build-clang install-clang clean-% build-baselib-% install-baselib-% build-runtimes-% install-runtimes-%

all: help

distclean:
	$(RM) -rf $(BUILD_ROOT_DIR)
clean-%:
	$(RM) -rf $(LLVM_RUNTIMES_BUILD_DIR_PREFIX)_$* $(LIB_NXOS_BUILD_DIR_PREFIX)_$* $(LIB_XLIBC_BUILD_DIR_PREFIX)_$*

build-clang: $(BUILD_ROOT_DIR)/clang.BUILT
install-clang: $(BUILD_ROOT_DIR)/clang.INSTALLED

build-baselib-%: $(BUILD_ROOT_DIR)/libxlibc_%/libxlibc.a ;
install-baselib-%: $(BUILD_ROOT_DIR)/libxlibc_%.INSTALLED ;

build-runtimes-%: $(BUILD_ROOT_DIR)/runtimes_%.BUILT ;
install-runtimes-%: $(BUILD_ROOT_DIR)/runtimes_%.INSTALLED install-baselib-% ;

help:
	@$(ECHO) "make help: Show the help message."
	@$(ECHO) "make distclan: Delete all generated files in build directory."
	@$(ECHO) "make build-clang: Build the clang,lld using the HOST_CC,HOST_CXX."
	@$(ECHO) "make build-baselib-<ARCH>: Build the libnxos,libxlibc using the clang."
	@$(ECHO) "make build-runtimes-<ARCH>: Build the libc++,libc++abi,compiler-rt using the clang."
	@$(ECHO) "make clean-<ARCH>: Clean the build for the arch."
	@$(ECHO) ""
	@$(ECHO) "make install-clang: Install the clang,lld."
	@$(ECHO) "make install-baselib-<ARCH>: Install the libnxos and libxlibc."
	@$(ECHO) "make install-runtimes-<ARCH>: Install the libc++,libc++abi,compiler-rt."
	@$(ECHO) ""
	@$(ECHO) "Available ARCHs: riscv64, x86"
	@$(ECHO) ""
	@$(ECHO) "Available Variables:"
	@$(ECHO) "* PREFIX: Set the install directory, current: $(PREFIX)."
	@$(ECHO) "* O: Set the build directory, current: $(BUILD_ROOT_DIR)."
	@$(ECHO) "* HOST_CC: Set the host c compiler to build clang."
	@$(ECHO) "* HOST_CXX: Set the host cxx compiler to build clang.."
	@$(ECHO) ""
	@$(ECHO) "Example: build and install riscv64 target."
	@$(ECHO) "1. make build-runtimes-riscv64"
	@$(ECHO) "2. make install-clang install-baselib-riscv64 install-runtimes-riscv64"
	@$(ECHO) ""



# =======================================================================================
# Stage1: Build the clang using host c++, then use clang to build baselibs and runtimes.
# =======================================================================================

ifneq (, $(shell which ninja))
# ninja is so faster than make.
CMAKE_GENERATOR = Ninja
else
CMAKE_GENERATOR = Unix Makefiles
endif

_HOST_CLANG_CONFIG_ARGS =
ifneq ($(HOST_CC),)
_HOST_CLANG_CONFIG_ARGS += -DCMAKE_C_COMPILER=$(HOST_CC)
endif
ifneq ($(HOST_CXX),)
_HOST_CLANG_CONFIG_ARGS += -DCMAKE_CXX_COMPILER=$(HOST_CXX)
endif

$(BUILD_ROOT_DIR)/clang.BUILT:
	@$(CMAKE) -S $(LLVM_PROJ_SRC_DIR)/llvm -B $(CLANG_BUILD_DIR) \
		-G "$(CMAKE_GENERATOR)" \
		-DCMAKE_BUILD_TYPE=MinSizeRel \
		-DLLVM_ENABLE_PROJECTS="clang;lld;compiler-rt" \
		-DLLVM_ENABLE_RUNTIMES="" \
		-DLLVM_BUILD_TOOLS=ON \
		-DLLVM_STATIC_LINK_CXX_STDLIB=ON \
		-DLLVM_INCLUDE_TESTS=OFF \
		-DLLVM_INCLUDE_UTILS=OFF \
		-DLLVM_INCLUDE_BENCHMARKS=OFF \
		-DLLVM_INCLUDE_EXAMPLES=OFF \
		-DLLVM_INSTALL_BINUTILS_SYMLINKS=ON \
		-DLLVM_INSTALL_TOOLCHAIN_ONLY=ON \
		-DLLVM_ENABLE_TERMINFO=OFF \
		-DLLVM_ENABLE_ZLIB=OFF \
		-DLLVM_ENABLE_ZSTD=OFF \
		-DLLVM_HAVE_LIBXAR=OFF \
		-DLLVM_ENABLE_LIBXML2=OFF \
		-DCLANG_DEFAULT_LINKER=lld \
		-DCLANG_DEFAULT_CXX_STDLIB=libc++ \
		-DCLANG_DEFAULT_RTLIB=compiler-rt \
		-DCLANG_DEFAULT_UNWINDLIB=none \
		-DCLANG_DEFAULT_OBJCOPY=llvm-objcopy \
		-DCMAKE_INSTALL_PREFIX=$(PREFIX) \
		$(_HOST_CLANG_CONFIG_ARGS) \

	@$(CMAKE) --build $(CLANG_BUILD_DIR) \
		--target clang \
		--target lld \
		--target llvm-ar \
		--target llvm-ranlib \
		--target llvm-objcopy \
		--target llvm-objdump \
		--target llvm-nm \
		--target llvm-strings \
		--target llvm-size \
		--target llvm-readobj \
		--target llvm-strip \
		--target builtins \
		-j
	@$(TOUCH) $@


$(BUILD_ROOT_DIR)/clang.INSTALLED: $(BUILD_ROOT_DIR)/clang.BUILT
	@$(CMAKE) --build $(CLANG_BUILD_DIR) \
		--target install-clang-stripped \
		--target install-clang-resource-headers-stripped \
		--target install-lld-stripped \
		--target install-llvm-ar-stripped \
		--target install-llvm-ranlib-stripped \
		--target install-llvm-objcopy-stripped \
		--target install-llvm-objdump-stripped \
		--target install-llvm-nm-stripped \
		--target install-llvm-strings-stripped \
		--target install-llvm-size-stripped \
		--target install-llvm-readobj-stripped \
		--target install-llvm-strip-stripped \
		--target install-builtins-stripped \
		-j
	@$(TOUCH) $@

# =======================================================================================
# Stage2: Use the clang built in Stage1 to build the llvm runtimes for each arch.
# =======================================================================================

MARCH_FLAGS_riscv64 := -march=rv64imafdc -mabi=lp64d -mcmodel=medany
MARCH_FLAGS_x86 := -march=i686 -m32

LLVM_AR = $(CLANG_BUILD_DIR)/bin/llvm-ar
LLVM_CLANG = $(CLANG_BUILD_DIR)/bin/clang
LLVM_MAKE = $(MAKE) LLVM=$(CLANG_BUILD_DIR)/bin/
LLVM_CMAKE = $(CMAKE) -DCMAKE_BUILD_TYPE=Release \
	-G "$(CMAKE_GENERATOR)" \
	-DCMAKE_C_COMPILER_WORKS=ON \
	-DCMAKE_CXX_COMPILER_WORKS=ON \
	-DCMAKE_ASM_COMPILER=$(CLANG_BUILD_DIR)/bin/clang \
	-DCMAKE_C_COMPILER=$(CLANG_BUILD_DIR)/bin/clang \
	-DCMAKE_CXX_COMPILER=$(CLANG_BUILD_DIR)/bin/clang++ \
	-DCMAKE_AR=$(CLANG_BUILD_DIR)/bin/llvm-ar \
	-DCMAKE_RANLIB=$(CLANG_BUILD_DIR)/bin/llvm-ranlib \
	-DCMAKE_STRIP=$(CLANG_BUILD_DIR)/bin/llvm-strip \
	-DCMAKE_NM=$(CLANG_BUILD_DIR)/bin/llvm-nm \
	-DCMAKE_OBJCOPY=$(CLANG_BUILD_DIR)/bin/llvm-objcopy \
	-DCMAKE_OBJDUMP=$(CLANG_BUILD_DIR)/bin/llvm-objdump \
	-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \

RUNTIMES_COMMON_FLAGS = -D_POSIX_TIMERS=200809L \
	-fno-builtin -fno-stack-protector -O3 \
	-ffunction-sections -fdata-sections -ffreestanding \
	-Wno-typedef-redefinition -Wno-user-defined-literals \
	-I$(LLVM_PROJ_SRC_DIR)/libunwind/include \
	-I$(LIB_NXOS_SRC_DIR)/src/include \
	-I$(LIB_XLIBC_SRC_DIR)/src/include \
	-I$(LIB_XLIBC_SRC_DIR)/src/arch/generic \


$(BUILD_ROOT_DIR)/libnxos_%/libnxos.a: $(BUILD_ROOT_DIR)/clang.BUILT
	@$(LLVM_MAKE) -C $(LIB_NXOS_SRC_DIR) all -j \
		O=$(LIB_NXOS_BUILD_DIR_PREFIX)_$* \
		ARCH=$*

$(BUILD_ROOT_DIR)/libxlibc_%/libxlibc.a: $(BUILD_ROOT_DIR)/libnxos_%/libnxos.a
# todo: add "-j" when the libxlibc fix compile error using "-j".
	@$(LLVM_MAKE) -C $(LIB_XLIBC_SRC_DIR) all \
		O=$(LIB_XLIBC_BUILD_DIR_PREFIX)_$* \
		COMPILE_SDK_DIR=../../libnxos/src/ \
		ARCH=$*


DSO_HANDLE_OBJ_FILE := CMakeFiles/dso_handle.dir/dso_handle.c.obj
$(BUILD_ROOT_DIR)/dso_handle_%/$(DSO_HANDLE_OBJ_FILE): $(SRC_ROOT_DIR)/src/dso_handle.c
	@$(LLVM_CMAKE) -S $(SRC_ROOT_DIR)/src \
		-B $(BUILD_ROOT_DIR)/dso_handle_$* \
		-DARCH=$*
	@$(CMAKE) --build $(BUILD_ROOT_DIR)/dso_handle_$* -j


$(BUILD_ROOT_DIR)/runtimes_%.BUILT: $(BUILD_ROOT_DIR)/dso_handle_%/$(DSO_HANDLE_OBJ_FILE) $(BUILD_ROOT_DIR)/libxlibc_%/libxlibc.a
	@$(LLVM_CMAKE) -S $(LLVM_PROJ_SRC_DIR)/runtimes \
		-DLLVM_ENABLE_RUNTIMES="libunwind;libcxx;libcxxabi;compiler-rt" \
		-DUNIX=ON \
		-DLLVM_COMPILER_CHECKED=ON \
		\
		-DLIBCXX_HAS_PTHREAD_API:BOOL=OFF \
		-DLIBCXX_HAS_EXTERNAL_THREAD_API:BOOL=OFF \
		-DLIBCXX_BUILD_EXTERNAL_THREAD_LIBRARY:BOOL=OFF \
		-DLIBCXX_HAS_WIN32_THREAD_API:BOOL=OFF \
		\
		-DLIBCXX_ENABLE_THREADS:BOOL=OFF \
		-DLIBCXX_ENABLE_SHARED:BOOL=OFF \
		-DLIBCXX_ENABLE_EXCEPTIONS:BOOL=OFF \
		-DLIBCXX_ENABLE_FILESYSTEM:BOOL=OFF \
		-DLIBCXX_ENABLE_FSTREAM:BOOL=OFF \
		-DLIBCXX_ENABLE_MONOTONIC_CLOCK=OFF \
		-DLIBCXX_ENABLE_RANDOM_DEVICE=OFF \
		\
		-DLIBCXX_INCLUDE_BENCHMARKS:BOOL=OFF \
		-DLIBCXX_INCLUDE_TESTS:BOOL=OFF \
		-DLIBCXX_CXX_ABI=libcxxabi \
		-DLIBCXX_HAS_MUSL_LIBC:BOOL=ON \
		-DLIBCXXABI_INCLUDE_TESTS:BOOL=OFF \
		-DLIBCXXABI_ENABLE_EXCEPTIONS:BOOL=OFF \
		-DLIBCXXABI_ENABLE_SHARED:BOOL=OFF \
		-DLIBCXXABI_SILENT_TERMINATE:BOOL=ON \
		-DLIBCXXABI_ENABLE_THREADS:BOOL=OFF \
		-DLIBCXXABI_HAS_PTHREAD_API:BOOL=OFF \
		-DLIBCXXABI_HAS_EXTERNAL_THREAD_API:BOOL=OFF \
		-DLIBCXXABI_BUILD_EXTERNAL_THREAD_LIBRARY:BOOL=OFF \
		-DLIBCXXABI_HAS_WIN32_THREAD_API:BOOL=OFF \
		-DCXX_SUPPORTS_NOSTDINCXX_FLAG:BOOL=ON \
		-DCXX_SUPPORTS_NOSTDLIBXX_FLAG:BOOL=ON \
		-DLIBUNWIND_IS_BAREMETAL:BOOL=ON \
		-DLIBUNWIND_REMEMBER_HEAP_ALLOC:BOOL=ON \
		-DLIBUNWIND_ENABLE_SHARED:BOOL=OFF \
		-DLIBUNWIND_ENABLE_THREADS:BOOL=OFF \
		-DLIBUNWIND_ENABLE_ASSERTIONS:BOOL=OFF \
		-DLIBUNWIND_ENABLE_PEDANTIC:BOOL=OFF \
		-DLIBUNWIND_INCLUDE_TESTS:BOOL=OFF \
		\
		-DCOMPILER_RT_BUILD_BUILTINS=ON \
		-DCOMPILER_RT_BUILD_ORC=OFF \
		-DCOMPILER_RT_BUILD_CRT=OFF \
		-DCOMPILER_RT_BUILD_LIBFUZZER=OFF \
		-DCOMPILER_RT_BUILD_MEMPROF=OFF \
		-DCOMPILER_RT_BUILD_PROFILE=OFF \
		-DCOMPILER_RT_BUILD_SANITIZERS=OFF \
		-DCOMPILER_RT_BUILD_XRAY=OFF \
		-DCOMPILER_RT_BUILD_GWP_ASAN=PFF \
		-DCOMPILER_RT_CRT_USE_EH_FRAME_REGISTRY=OFF \
		-DCOMPILER_RT_BAREMETAL_BUILD=OFF \
		-DCOMPILER_RT_INCLUDE_TESTS=OFF \
		-DCOMPILER_RT_HAS_FPIC_FLAG=OFF \
		-DCOMPILER_RT_BAREMETAL_BUILD=ON \
		-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON \
		\
		-B $(LLVM_RUNTIMES_BUILD_DIR_PREFIX)_$* \
		-DCMAKE_INSTALL_PREFIX="$(INSTALL_SYSROOTS_PREFIX)/$*/" \
		-DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF \
		-DCOMPILER_RT_INSTALL_LIBRARY_DIR="$(INSTALL_SYSROOTS_PREFIX)/$*/lib" \
		-DCMAKE_ASM_FLAGS="$(RUNTIMES_COMMON_FLAGS) -I$(LIB_XLIBC_SRC_DIR)/src/arch/$*/include $(MARCH_FLAGS_$*)" \
		-DCMAKE_C_FLAGS="$(RUNTIMES_COMMON_FLAGS) -I$(LIB_XLIBC_SRC_DIR)/src/arch/$*/include $(MARCH_FLAGS_$*)" \
		-DCMAKE_CXX_FLAGS="$(RUNTIMES_COMMON_FLAGS) -I$(LIB_XLIBC_SRC_DIR)/src/arch/$*/include $(MARCH_FLAGS_$*)" \
		-DCMAKE_TOOLCHAIN_FILE=$(CMAKE_TOOLCHAIN_DIR)/$*.cmake

# -DCOMPILER_RT_INSTALL_LIBRARY_DIR=$(PREFIX)/lib/clang/$(word 1,$(subst ., ,$(word 3,$(shell $(CLANG_BUILD_DIR)/bin/clang --version))))/lib \

	@$(CMAKE) --build $(LLVM_RUNTIMES_BUILD_DIR_PREFIX)_$* -j

	@$(LLVM_AR) rs $(LLVM_RUNTIMES_BUILD_DIR_PREFIX)_$*/lib/libc++.a $<

	@$(TOUCH) $@

# =======================================================================================
# Stage3: install llvm runtimes, baselibs, and scripts for each arch.
# =======================================================================================

$(BUILD_ROOT_DIR)/runtimes_%.INSTALLED: $(BUILD_ROOT_DIR)/runtimes_%.BUILT
	@$(CMAKE) --install $(LLVM_RUNTIMES_BUILD_DIR_PREFIX)_$*

#todo: delete this patch
	if [ "$*" = "x86" ]; then \
		$(MKDIR) "$(PREFIX)/lib/clang/$(word 1,$(subst ., ,$(word 3,$(shell $(LLVM_CLANG) --version))))/lib/hurd" ; \
		$(LINK) $(INSTALL_SYSROOTS_PREFIX)/$*/lib/libclang_rt.builtins-i386.a "$(PREFIX)/lib/clang/$(word 1,$(subst ., ,$(word 3,$(shell $(LLVM_CLANG) --version))))/lib/hurd/libclang_rt.builtins-i386.a" ; \
	fi

	@$(TOUCH) $@


$(BUILD_ROOT_DIR)/libnxos_%.INSTALLED: $(BUILD_ROOT_DIR)/libnxos_%/libnxos.a
ifeq ($(wildcard $(PREFIX)/share/cmake),)
	$(MKDIR) $(PREFIX)/share/cmake
endif
ifeq ($(wildcard $(PREFIX)/share/ldscripts),)
	$(MKDIR) $(PREFIX)/share/ldscripts
endif
ifeq ($(wildcard $(PREFIX)/share/cmake/common.cmake),)
	$(CP) $(SRC_ROOT_DIR)/share/cmake/common.cmake $(PREFIX)/share/cmake/common.cmake
endif

	$(MKDIR) $(INSTALL_SYSROOTS_PREFIX)/$*/include
	$(MKDIR) "$(INSTALL_SYSROOTS_PREFIX)/$*/lib"

	$(CP) $(LIB_NXOS_SRC_DIR)/src/include/* $(INSTALL_SYSROOTS_PREFIX)/$*/include/
	$(CP) $(BUILD_ROOT_DIR)/libnxos_$*/libnxos.a $(INSTALL_SYSROOTS_PREFIX)/$*/lib/libnxos.a
	$(CP) $(SRC_ROOT_DIR)/share/cmake/$*.cmake $(PREFIX)/share/cmake/$*.cmake
	$(CP) $(SRC_ROOT_DIR)/share/ldscripts/$*.ld $(PREFIX)/share/ldscripts/$*.ld
	@$(TOUCH) $@


$(BUILD_ROOT_DIR)/libxlibc_%.INSTALLED: $(BUILD_ROOT_DIR)/libxlibc_%/libxlibc.a $(BUILD_ROOT_DIR)/libnxos_%.INSTALLED
	$(CP) $(BUILD_ROOT_DIR)/libxlibc_$*/libxlibc.a $(INSTALL_SYSROOTS_PREFIX)/$*/lib/libc.a
	$(CP) $(LIB_XLIBC_SRC_DIR)/src/include/* $(INSTALL_SYSROOTS_PREFIX)/$*/include/
	$(CP) $(LIB_XLIBC_SRC_DIR)/src/arch/generic/* $(INSTALL_SYSROOTS_PREFIX)/$*/include/
	$(CP) $(LIB_XLIBC_SRC_DIR)/src/arch/$*/include/* $(INSTALL_SYSROOTS_PREFIX)/$*/include/

# create empty libraries.
	$(LLVM_AR) rcs $(INSTALL_SYSROOTS_PREFIX)/$*/lib/libm.a
	$(LLVM_AR) rcs $(INSTALL_SYSROOTS_PREFIX)/$*/lib/libdl.a
	$(LLVM_AR) rcs $(INSTALL_SYSROOTS_PREFIX)/$*/lib/librt.a

	@$(TOUCH) $@


.NOTINTERMEDIATE: $(BUILD_ROOT_DIR)/clang.BUILT $(BUILD_ROOT_DIR)/libnxos_%/libnxos.a $(BUILD_ROOT_DIR)/libxlibc_%/libxlibc.a $(BUILD_ROOT_DIR)/dso_handle_%/$(DSO_HANDLE_OBJ_FILE) $(BUILD_ROOT_DIR)/runtimes_%.BUILT $(BUILD_ROOT_DIR)/runtimes_%.INSTALLED $(BUILD_ROOT_DIR)/libnxos_%.INSTALLED $(BUILD_ROOT_DIR)/libxlibc_%.INSTALLED
